# -*- coding: utf-8 -*-
# Author   : ZhangQing
# Time     : 2025-07-15 23:10
# File     : base_adapter.py
# Project  : dynamic-portfolio-optimizer
# Desc     : 基础适配器

from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Any
import pandas as pd
from datetime import datetime, timedelta
import logging
from ratelimit import limits, sleep_and_retry
import requests_cache
from functools import wraps

logger = logging.getLogger(__name__)


class RateLimitDecorator:
    """限流装饰器"""

    def __init__(self, calls: int = 5, period: int = 1):
        self.calls = calls
        self.period = period

    def __call__(self, func):
        @sleep_and_retry
        @limits(calls=self.calls, period=self.period)
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        return wrapper


class BaseDataAdapter(ABC):
    """数据适配器基类"""

    def __init__(self, config: 'DataSourceConfig'):
        self.config = config
        self.session = requests_cache.CachedSession(
            cache_name=f'cache_{config.name.lower().replace(" ", "_")}',
            expire_after=timedelta(hours=1)
        )
        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")

    @abstractmethod
    def get_stock_data(self, symbol: str, start_date: datetime,
                       end_date: datetime, interval: str = '1d') -> pd.DataFrame:
        """获取股票数据"""
        pass

    @abstractmethod
    def get_options_data(self, symbol: str, expiration_date: datetime) -> pd.DataFrame:
        """获取期权数据"""
        pass

    @abstractmethod
    def get_fundamentals(self, symbol: str) -> Dict[str, Any]:
        """获取基本面数据"""
        pass

    @abstractmethod
    def get_news(self, symbol: str, limit: int = 10) -> List[Dict[str, Any]]:
        """获取新闻数据"""
        pass

    def validate_data(self, data: pd.DataFrame) -> bool:
        """验证数据质量"""
        if data.empty:
            self.logger.warning("数据为空")
            return False

        # 检查必要列
        required_columns = ['open', 'high', 'low', 'close', 'volume']
        missing_columns = [col for col in required_columns if col not in data.columns]

        if missing_columns:
            self.logger.warning(f"缺失列: {missing_columns}")
            return False

        # 检查数据完整性
        if data.isnull().sum().sum() > len(data) * 0.1:  # 超过10%的缺失值
            self.logger.warning("数据缺失过多")
            return False

        return True

    def standardize_data(self, data: pd.DataFrame) -> pd.DataFrame:
        """标准化数据格式"""
        if data.empty:
            return data

        # 统一列名
        column_mapping = {
            'Open': 'open',
            'High': 'high',
            'Low': 'low',
            'Close': 'close',
            'Volume': 'volume',
            'Adj Close': 'adj_close'
        }

        data = data.rename(columns=column_mapping)

        # 确保数字类型
        numeric_columns = ['open', 'high', 'low', 'close', 'volume']
        for col in numeric_columns:
            if col in data.columns:
                data[col] = pd.to_numeric(data[col], errors='coerce')

        # 排序
        if isinstance(data.index, pd.DatetimeIndex):
            data = data.sort_index()

        return data

